﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace VirtualOperator
{
    public class CallController
    {
        private class DirectoryEntry
        {
            public int Id;
            public string Name;
            public Socket Socket;
            public byte[] Buffer = new byte[4086];
            public NwTTP NwTtp;

            public bool Connected { get { return Socket.Connected; } }
            
            public DirectoryEntry(NwTTP nwttp, int id,  string name, Socket socket)
            {
                this.Socket = socket;
                this.NwTtp = nwttp;
                this.Id = id;
                this.Name = name;
            }
        }
        private Dictionary<string, DirectoryEntry> Clients = new Dictionary<string, DirectoryEntry>();

        private VirtualNetwork network;
        private Dictionary<int, CallerConnection> connections = new Dictionary<int, CallerConnection>();


        private string VNOName;



        public CallController(VirtualNetwork network, string vnoName)
        {
            this.network = network;
            //this.rc = new RoutingController(manager)
            VNOName = vnoName;
        }
        int index=0;
        public void AddNwTTP(NwTTP nwttp, int port)
        {
            index++;
            try
            {
                DirectoryEntry entry;
                Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                IPEndPoint server = new IPEndPoint(IPAddress.Loopback, port);
                socket.Connect(server);
                byte[] buffer = new byte[4086];
                int r = socket.Receive(buffer);
                string[] query = Encoding.ASCII.GetString(buffer, 0, r).Split(' ');
 
                if (query.Length ==3 && query[0]=="REGISTER")
                {
                    int id = Int32.Parse(query[1]);
                    entry = new DirectoryEntry(nwttp, id, query[2], socket);
                    Clients.Add(query[2], entry);
                    socket.BeginReceive(entry.Buffer, 0,entry.Buffer.Length, SocketFlags.None, OnDataReceived, entry);
                    socket.Send(Encoding.ASCII.GetBytes("OK " + VNOName));
                }
            }
            catch (Exception) {
                Thread.Sleep(100);
                if (index < 10)
                    AddNwTTP(nwttp, port);
                else index = 0;
            }
        
        
        }

        private void OnDataReceived(IAsyncResult asyn)
        {
            DirectoryEntry client = (DirectoryEntry)asyn.AsyncState;
            int r = 0;
            try { r = client.Socket.EndReceive(asyn); }
            catch (SocketException)
            {
                client.Socket.Close();
                Clients.Remove(client.Name);
                return;
            }
            if (r == 0)
            {
                client.Socket.Close();
                Clients.Remove(client.Name);
                return;
            }
            string recv = Encoding.ASCII.GetString(client.Buffer, 0, r);
            ProcessQuery(recv, client);
            client.Socket.BeginReceive(client.Buffer, 0, client.Buffer.Length, SocketFlags.None, OnDataReceived, client);
        }

        private void ProcessQuery(string recv, DirectoryEntry client)
        {
            string[] query = recv.Split(' ');
            if (query.Length < 2)
                return;
            int id = Int32.Parse(query[1]);
            CallerConnection connection;
            if(connections.ContainsKey(id))
                connection = connections[id];
            else
                connection = new CallerConnection();
                
          
            switch (query[0])
            { 
                case "INVITE":
                    if(connection.ParseInvite(query))
                    {
                        if (Clients.ContainsKey(connection.SourceName) && Clients.ContainsKey(connection.TargetName))
                        {
                            var targetClient = Clients[connection.TargetName];
                            connection.FillMissingSpots(targetClient.Id, targetClient.Name);
                            
                            connections.Add(connection.ConnectionRand, connection);
                            Send(connection.ToTrying(), client.Socket);
                            UserConnection uc = network.SetupConnection(client.NwTtp, targetClient.NwTtp, connection.Capacity);
                            if (uc != null)
                            {
                                connection.CallId = uc.ID;
                                Send(connection.ToInvite(), targetClient.Socket);

                            }
                            else Send(connection.ToCancel(), client.Socket);
                        }
                        else Send(connection.ToCancel(), client.Socket);
                        
                    }
               
                    break;
                case "TRYING":
                    connection.ParseTrying(query);
                    break;
                case "RINGING":
                    if (connection.UniType == UniType.SWITCHED)
                    {
                        var sourceClient = Clients[connection.SourceName];
                        if (connection.ParseRinging(query))
                            Send(connection.ToRinging(), sourceClient.Socket);
                    }
                    else connection.ParseRinging(query);
                    break;
                case "OK":
                    if (connection.UniType == UniType.SWITCHED)
                    {
                        var sourceClient2 = Clients[connection.SourceName];
                        if (connection.ParseOK(query))
                            Send(connection.ToOK(), sourceClient2.Socket);                      
                    }
                    else
                        connection.ParseOK(query);

                    break;
                case "BYE":
                    if (client.NwTtp.Type == Direction.SOURCE)
                    {
                        var target = Clients[connection.TargetName];
                        if(connection.Connected)
                        target.Socket.Send(Encoding.ASCII.GetBytes(connection.ToBye()));
                        connections.Remove(id);
                    }
                    break;
                case "CANCEL":
                    connections.Remove(connection.ConnectionRand);
                    break;
                default: break;
            }
          
        }
        private void Send(string packet, Socket socket)
        {
                socket.Send(Encoding.ASCII.GetBytes(packet));
        }

        public void TeardownCall(UserConnection uc)
        {
            var target = Clients[uc.TargetNwTTP.Name];
            var source = Clients[uc.SourceNwTTP.Name];
            int id=-1;
            foreach(KeyValuePair<int, CallerConnection> c in connections)
            {
                if (c.Value.CallId == uc.ID)
                { id = c.Key; break; }
            }
                 if (id > 0)
                     {
                         Send(connections[id].ToBye(), source.Socket);
                         Send(connections[id].ToBye(), target.Socket);
                         connections.Remove(id);
                     }
            
        }

        public void BeginCall(UserConnection uc)
        {            
            var target = Clients[uc.TargetNwTTP.Name];
            var source = Clients[uc.SourceNwTTP.Name];
            CallerConnection connection = new CallerConnection(source.Name, source.Id, target.Name, target.Id, uc.Capacity, UniType.SOFT);
            connection.CallId = uc.ID;
            connections.Add(connection.ConnectionRand, connection);
            Send(connection.ToInvite(), source.Socket);
            Send(connection.ToInvite(), target.Socket);        
        }


        private string UpperCaseFirstLetter(string s)
        {
            // Check for empty string.
            if (string.IsNullOrEmpty(s))
            {
                return string.Empty;
            }
            // Return char and concat substring.
            return char.ToUpper(s[0]) + s.Substring(1);
        }
    }

}

